﻿using System;
using System.Collections.Generic;
using System.Text;
using Mesuro.Common;
using System.Windows.Media;
using System.Windows;
using Mesuro.Graphing.Utilities;

using System.Collections;
using System.Diagnostics;
using System.Windows.Shapes;
using System.Windows.Controls;
using Vector = Mesuro.Common.Vector;

namespace Mesuro.Graphing.Plots {
	public static class Plots3D {

		#region DP: MeshCellsX
		public static readonly DependencyProperty MeshCellsXProperty = DependencyProperty.RegisterAttached( "MeshCellsX", typeof( int ), typeof( Plots3D ), new FrameworkPropertyMetadata( 24, FrameworkPropertyMetadataOptions.None, MeshCellsXChangedStatic ) );
		public static int GetMeshCellsX( Plot element ) { return (int)element.GetValue( MeshCellsXProperty ); }
		public static void SetMeshCellsX( Plot element, int value ) { element.SetValue( MeshCellsXProperty, value ); }
		private static void MeshCellsXChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) {
			UIElement el = sender as UIElement;
			if (el != null)
				el.RaiseEvent( new RoutedPropertyChangedEventArgs<int>( (int)e.OldValue, (int)e.NewValue, MeshCellsXChangedEvent ) );
		}

		public static readonly RoutedEvent MeshCellsXChangedEvent = EventManager.RegisterRoutedEvent( "MeshCellsXChanged", RoutingStrategy.Direct, typeof( RoutedPropertyChangedEventArgs<int> ), typeof( Plots3D ) );
		public static void AddMeshCellsXChangedHandler( DependencyObject d, RoutedPropertyChangedEventHandler<int> handler ) {
			if (d != null && d is UIElement)
				((UIElement)d).AddHandler( MeshCellsXChangedEvent, handler );
		}
		public static void RemoveMeshCellsXChangedHandler( DependencyObject d, RoutedPropertyChangedEventHandler<int> handler ) {
			if (d != null && d is UIElement)
				((UIElement)d).RemoveHandler( MeshCellsXChangedEvent, handler );
		}
		#endregion

		#region DP: MeshCellsY
		public static readonly DependencyProperty MeshCellsYProperty = DependencyProperty.RegisterAttached( "MeshCellsY", typeof( int ), typeof( Plot ), new FrameworkPropertyMetadata( 24, FrameworkPropertyMetadataOptions.None, MeshCellsYChangedStatic ) );
		public static int GetMeshCellsY( Plot element ) { return (int)element.GetValue( MeshCellsYProperty ); }
		public static void SetMeshCellsY( Plot element, int value ) { element.SetValue( MeshCellsYProperty, value ); }
		private static void MeshCellsYChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) {
			UIElement el = sender as UIElement;
			if (el != null)
				el.RaiseEvent( new RoutedPropertyChangedEventArgs<int>( (int)e.OldValue, (int)e.NewValue, MeshCellsYChangedEvent ) );
		}

		public static readonly RoutedEvent MeshCellsYChangedEvent = EventManager.RegisterRoutedEvent( "MeshCellsYChanged", RoutingStrategy.Direct, typeof( RoutedPropertyChangedEventArgs<int> ), typeof( Plots3D ) );
		public static void AddMeshCellsYChangedHandler( DependencyObject d, RoutedPropertyChangedEventHandler<int> handler ) {
			if (d != null && d is UIElement)
				((UIElement)d).AddHandler( MeshCellsYChangedEvent, handler );
		}
		public static void RemoveMeshCellsYChangedHandler( DependencyObject d, RoutedPropertyChangedEventHandler<int> handler ) {
			if (d != null && d is UIElement)
				((UIElement)d).RemoveHandler( MeshCellsYChangedEvent, handler );
		}
		#endregion

		#region DP: NoDataColor
		public static readonly DependencyProperty NoDataColorProperty = DependencyProperty.RegisterAttached( "NoDataColor", typeof( Color ), typeof( Plots3D ), new FrameworkPropertyMetadata( Colors.Transparent, FrameworkPropertyMetadataOptions.None, NoDataColorChangedStatic ) );
		public static Color GetNoDataColor( DependencyObject element ) { return (Color)element.GetValue( NoDataColorProperty ); }
		public static void SetNoDataColor( DependencyObject element, Color value ) { element.SetValue( NoDataColorProperty, value ); }
		private static void NoDataColorChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) { }
		#endregion

		#region DP: ColorCoefficients
		public static readonly DependencyProperty ColorCoefficientsProperty = DependencyProperty.RegisterAttached( "ColorCoefficients", typeof( Color ), typeof( Plots3D ), new FrameworkPropertyMetadata( Colors.Lime, FrameworkPropertyMetadataOptions.None, ColorCoefficientsChangedStatic ) );
		public static Color GetColorCoefficients( DependencyObject element ) { return (Color)element.GetValue( ColorCoefficientsProperty ); }
		public static void SetColorCoefficients( DependencyObject element, Color value ) { element.SetValue( ColorCoefficientsProperty, value ); }
		private static void ColorCoefficientsChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) { }
		#endregion

		#region DP: ColorOffsets
		public static readonly DependencyProperty ColorOffsetsProperty = DependencyProperty.RegisterAttached( "ColorOffsets", typeof( Color ), typeof( Plots3D ), new FrameworkPropertyMetadata( Color.FromArgb( 255, 0, 0, 0 ), FrameworkPropertyMetadataOptions.None, ColorOffsetsChangedStatic ) );
		public static Color GetColorOffsets( DependencyObject element ) { return (Color)element.GetValue( ColorOffsetsProperty ); }
		public static void SetColorOffsets( DependencyObject element, Color value ) { element.SetValue( ColorOffsetsProperty, value ); }
		private static void ColorOffsetsChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) { }
		#endregion

		static Plots3D() {
			GraphingRegistry.RegisterRenderer(
				PLOT_ChromaticMap,
				typeof( ChromaticMapRendererContent ),
				new Type[] { typeof( Plot ) },
				new object[] { null }, null
			);
		}
		public static void Load() {
			//NOP
			//This function provides a way to force the static initialiser to be called
		}

		public const string PLOT_ChromaticMap = "Chromatic Map";
		private class ChromaticMapRendererContent : FrameworkElement, IPlotContent {

			private readonly Plot plot;
			private Rectangle[,] cells = new Rectangle[0, 0];


			public ChromaticMapRendererContent( Plot plot ) {
				this.plot = plot;
				AddMeshCellsXChangedHandler( plot, CellsChanged );
				AddMeshCellsYChangedHandler( plot, CellsChanged );
			}

			private void CellsChanged( Object sender, RoutedPropertyChangedEventArgs<int> e ) {
				Update();
			}

			public void Update() {
				//Get Content size
				int nX = GetMeshCellsX( plot );
				int nY = GetMeshCellsY( plot );
				Color Coefficients = GetColorCoefficients( plot );
				Color Offsets = GetColorOffsets( plot );
				double
					cA = Coefficients.A / 255d,
					cR = Coefficients.R / 255d,
					cG = Coefficients.G / 255d,
					cB = Coefficients.B / 255d,
					oA = 2 * (Offsets.A - 127),
					oR = 2 * (Offsets.R - 127),
					oG = 2 * (Offsets.G - 127),
					oB = 2 * (Offsets.B - 127);

				Color NoData = GetNoDataColor( plot );

				//See if we have the correct cell layout
				if (nX != cells.GetLength( 0 ) || nY != cells.GetLength( 1 ))
					create( nX, nY );

				ISeries series = plot.PlotData;

				double offX = series.Channels[0].Limits.Min;
				double offY = series.Channels[1].Limits.Min;
				double offZ = series.Channels[2].Limits.Min;

				double scaX = 1025d / 1024 * series.Channels[0].Limits.Range / nX;
				double scaY = 1025d / 1024 * series.Channels[1].Limits.Range / nY;
				double scaZ = 1025d / 1024 * series.Channels[2].Limits.Range / 0xFF;

				byte[,] nPoints = new byte[nX, nY];
				byte[,] data = new byte[nX, nY];

				//Calculate heights
				foreach (Vector v in series) {

					int x = (int)((v[0] - offX) / scaX);
					int y = (int)((v[1] - offY) / scaY);
					int z = (int)((v[2] - offZ) / scaZ);

					double n = nPoints[x, y]++;

					data[x, y] = (byte)(data[x, y] * ((n - 1d) / n) + z / n);

				}
				//Fill in rectangles
				Brush NoDataBrush = new SolidColorBrush( NoData );
				for (int i = 0; i < nX; i++) {
					for (int j = 0; j < nY; j++) {
						cells[i, j].Fill = (nPoints[i, j] > 0) ? new SolidColorBrush( Color.FromArgb(
							(byte)Math.Min( 0xFF, cA * data[i, j] + oA ),
							(byte)Math.Min( 0xFF, cR * data[i, j] + oR ),
							(byte)Math.Min( 0xFF, cG * data[i, j] + oG ),
							(byte)Math.Min( 0xFF, cB * data[i, j] + oB )
						) ) : NoDataBrush;
					}
				}

			}

			private void create( int nX, int nY ) {

				removeAll();

				cells = new Rectangle[nX, nY];

				for (int i = 0; i < nX; i++) {
					for (int j = 0; j < nY; j++) {
						cells[i, j] = new Rectangle();
						AddVisualChild( cells[i, j] );
					}
				}
				InvalidateArrange();
			}

			private void removeAll() {
				foreach (Rectangle r in cells) {
					RemoveVisualChild( r );
				}
			}

			protected override Size ArrangeOverride( Size finalSize ) {

				int nX = cells.GetLength( 0 ), nY = cells.GetLength( 1 );

				double cellW = ActualWidth / nX, cellH = ActualHeight / nY;

				Size sz = new Size( cellW, cellH );

				for (int i = 0; i < nX; i++) {
					for (int j = 0; j < nY; j++) {
						cells[i, j].Measure( sz );
						cells[i, j].Arrange( new Rect( i * cellW, j * cellH, cellW, cellH ) );
					}
				}
				return finalSize;
			}

			protected override void OnRenderSizeChanged( SizeChangedInfo sizeInfo ) {
				base.OnRenderSizeChanged( sizeInfo );
				InvalidateArrange();
			}

			protected override int VisualChildrenCount {
				get {
					return cells.Length;
				}
			}
			protected override Visual GetVisualChild( int index ) {
				return cells[index / cells.GetLength( 1 ), index % cells.GetLength( 1 )];
			}
		}
	}
}
